﻿package
{
	import Box2D.Dynamics.*;
	import Box2D.Collision.*;
	import Box2D.Collision.Shapes.*;
	import Box2D.Common.Math.*;
	import graph.*;
	
	/**
	 * ...
	 * @author s
	 */
	public class SpringGraph extends Graph	{
		private var _b2world:b2World;
		
		private var _dragNode:Node;
		private var _dragX:Number;
		private var _dragY:Number;
		
		private const SCALE:Number = 0.1;
		
		public function SpringGraph(width:Number, height:Number)
		{
			super();

			//init box2d
			var worldAABB:b2AABB = new b2AABB();
			worldAABB.lowerBound.Set(0, 0);
			worldAABB.upperBound.Set(width * SCALE, height * SCALE);
			var gravity:b2Vec2 = new b2Vec2(0.0, 0.0);
			_b2world = new b2World(worldAABB, gravity, true);
			
			createWall(width, height);
		}

		private function createWall(worldWidth:Number, worldHeight:Number):void
		{
			const WALL_THICK:Number = 10.0;
			var wallObject:b2PolygonDef = new b2PolygonDef();
			wallObject.density = 0.0;
			var wallDef:b2BodyDef = new b2BodyDef();
			var wallBody:b2Body;

			//Left
			wallDef.position.Set(WALL_THICK * SCALE, worldHeight / 2 * SCALE);
			wallObject.SetAsBox(WALL_THICK * SCALE, worldHeight / 2 * SCALE);
			wallBody = _b2world.CreateBody(wallDef);
			wallBody.CreateShape(wallObject);
			wallBody.SetMassFromShapes();

			//Right
			wallDef.position.Set((worldWidth - WALL_THICK) * SCALE, worldHeight / 2 * SCALE);
			wallObject.SetAsBox(WALL_THICK * SCALE, worldHeight / 2 * SCALE);
			wallBody = _b2world.CreateBody(wallDef);
			wallBody.CreateShape(wallObject);
			wallBody.SetMassFromShapes();

			//Top
			wallDef.position.Set(worldWidth / 2 * SCALE, WALL_THICK * SCALE);
			wallObject.SetAsBox(worldWidth / 2 * SCALE, WALL_THICK * SCALE);
			wallBody = _b2world.CreateBody(wallDef);
			wallBody.CreateShape(wallObject);
			wallBody.SetMassFromShapes();

			//Bottom
			wallDef.position.Set(worldWidth / 2 * SCALE, (worldHeight - WALL_THICK) * SCALE);
			wallObject.SetAsBox(worldWidth / 2 * SCALE, WALL_THICK * SCALE);
			wallBody = _b2world.CreateBody(wallDef);
			wallBody.CreateShape(wallObject);
			wallBody.SetMassFromShapes();
		}

		public function addNodeToPosition(node:Node, x:Number, y:Number):void 
		{
			var bodyDef:b2BodyDef = new b2BodyDef();
			bodyDef.position.x = x * SCALE;
			bodyDef.position.y = y * SCALE;
			bodyDef.userData = node;
			var circleDef:b2CircleDef = new b2CircleDef();
			circleDef.radius = 1.0;
			circleDef.density = 1.0;
			circleDef.friction = 0.1;
			circleDef.restitution = 0.9;
			var body:b2Body = _b2world.CreateBody(bodyDef);
			body.CreateShape(circleDef);
			body.SetMassFromShapes();
			node.userData = body;

			super.addNode(node);
		}
		
		public function getNodeX(node:Node):Number 
		{
			return (node.userData as b2Body).GetPosition().x / SCALE;
		}

		public function getNodeY(node:Node):Number
		{
			return (node.userData as b2Body).GetPosition().y / SCALE;
		}
		
		public function setDragNode(node:Node, dragX:Number, dragY:Number):void 
		{
			_dragNode = node;
			_dragX = dragX;
			_dragY = dragY;
		}
		
		public function step(timeStep:Number, iterations:int):void 
		{
			var i:int;
			var edge:Edge;
			var srcBody:b2Body;
			var dstBody:b2Body;
			var body:b2Body;
			var dx:Number;
			var dy:Number;
			var d:Number;   // distance
			var f:Number;	// force
			
			//init force
			for (i = 0; i < super._nodes.length; i++) {
				body = super._nodes[i].userData as b2Body;
				body.m_force.x = 0;
				body.m_force.y = 0;
			}

			// calc force
			for (i = 0; i < super._edges.length; i++) {
				edge = super._edges[i];
				srcBody = edge.srcNode.userData as b2Body;
				dstBody = edge.dstNode.userData as b2Body;

				dx = dstBody.GetPosition().x - srcBody.GetPosition().x;
				dy = dstBody.GetPosition().y - srcBody.GetPosition().y;
				d = Math.sqrt(dx * dx + dy * dy);
				f = edge.weight * d * 1.0;
				srcBody.m_force.x += f * dx / d;
				srcBody.m_force.y += f * dy / d;
				dstBody.m_force.x -= f * dx / d;
				dstBody.m_force.y -= f * dy / d;
				if (isNaN(srcBody.m_force.x) || isNaN(srcBody.m_force.y)) {
					trace("'" + edge.srcNode.label + "' -> '" + edge.dstNode.label + "'");
				}
			}
			
			//calc drag force
			if (_dragNode != null) {
				srcBody = _dragNode.userData as b2Body;

				dx = _dragX * SCALE - srcBody.GetPosition().x;
				dy = _dragY * SCALE - srcBody.GetPosition().y;
				d = Math.sqrt(dx * dx + dy * dy);
				f = 100.0 * d;
				srcBody.m_force.x += f * dx / d;
				srcBody.m_force.y += f * dy / d;
			}

			for (i = 0; i < super._nodes.length; i++) {
				body = super._nodes[i].userData as b2Body;
				if (isNaN(body.m_force.x) || isNaN(body.m_force.y)) {
					body.m_force.Set(0, 0);
				}
				//body.ApplyForce(body.m_force, body.GetPosition());
			}

			_b2world.Step(timeStep, iterations);
		}
/*
		public function stepOld():void
		{
			var i:int;
			var j:int;
			var node:Node;
			var srcNode:Node;
			var dstNode:Node;
			var edge:Edge;
			var dx:Number;
			var dy:Number;
			var d:Number;   // distance
			var v:Number;	// velocity
			var f:Number;	// force

			//init force
			for (i = 0; i < super._nodes.length; i++) {
				node = super._nodes[i];
				node._fx = 0;
				node._fy = 0;
			}
			
			// calc force by edge
			for (i = 0; i < super._edges.length; i++) {
				edge = super._edges[i];
				srcNode = edge.srcNode;
				dstNode = edge.dstNode;

				dx = dstNode._x - srcNode._x;
				dy = dstNode._y - srcNode._y;
				d = Math.sqrt(dx * dx + dy * dy);
				f = edge.weight * d * 0.0001;
				srcNode._fx += f * dx / d;
				srcNode._fy += f * dy / d;
				dstNode._fx -= f * dx / d;
				dstNode._fy -= f * dy / d;
			}

			// calc repelling force
			for (i = 0; i < super._nodes.length; i++) {
				for (j = i + 1; j < super._nodes.length; j++) {
					srcNode = super._nodes[i];
					dstNode = super._nodes[j];

					dx = dstNode._x - srcNode._x;
					dy = dstNode._y - srcNode._y;
					d = Math.sqrt(dx * dx + dy * dy);
					f = 0.1 / (d * d);
					srcNode._fx -= f * dx / d;
					srcNode._fy -= f * dy / d;
					dstNode._fx += f * dx / d;
					dstNode._fy += f * dy / d;
				}
			}
			
			//calc frictional force, velocity and potision
			for (i = 0; i < super._nodes.length; i++) {
				node = super._nodes[i];
				if (isNaN(node._fx) || isNaN(node._fy)) {
					continue;
				}
				
				node._vx += node._fx;
				node._vy += node._fy;

				const vMax:Number = 3.0;
				v = Math.sqrt(node._vx * node._vx + node._vy * node._vy);
				if (v >= vMax) {
					node._vx = node._vx * vMax / v;
					node._vy = node._vy * vMax / v;
				}

				// calc frictional force
				v = Math.sqrt(node._vx * node._vx + node._vy * node._vy);
				if(v > 0){
					const mu:Number = 0.0001;
					var frictionalForce:Number = node.weight * mu;
					if (frictionalForce > v) {
						frictionalForce = v;
					}
					node._vx -= frictionalForce * node._vx / v;
					node._vy -= frictionalForce * node._vy / v;
				}

				node._x += node._vx;
				node._y += node._vy;
			}
		}*/
	}
}